﻿using System;
using System.Collections.Generic;
using System.Linq;
using Gumini.JulaDB.Data;
using Gumini.JulaDB.DataTypes;
using Gumini.JulaDB.Enums;
using Gumini.JulaDB.Expressions;
using Gumini.JulaDB.Provider;

namespace Gumini.JulaDB.Planner
{
    /// <summary>
    /// Represent filtering operation (WHERE clause).
    /// </summary>
    internal class FilterNode : UnaryPlanNode
    {
        private readonly Expression _expression;
        
        public FilterNode(PlanNode child, Expression expression)
            : base(child)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (expression.IsAggregate)
                throw new JulaDbException(ErrorCode.AggregateMismatch, "Cannot use aggregate expresions in the WHERE condition.");

            _expression = expression;

            var context = child.Heading.GetTypeContext();
            var expressionDataType = _expression.InferType(context);

            CheckDataType(expressionDataType);
        }

        internal Expression Expression
        {
            get { return _expression; }
        }

        private bool ShouldUseRow(Row row)
        {
            // creating an evaluation context with all field names and values
            var context = row.GetEvaluationContext(Heading);

            // evaluating the where clause
            var evaluationResult = _expression.Evaluate(context);

            if (!evaluationResult.IsNull)
                CheckDataType(evaluationResult.Type);

            // check whether this row will be affected by the query
            bool? useRow = (bool?)evaluationResult.Value;
            return useRow.HasValue && useRow.Value;
        }

        public override List<Row> Execute()
        {
            try
            {
                return Child.Rows.
                    AsParallel().
                    Where(r => ShouldUseRow(r)).
                    ToList();
            }
            // because we use AsParallel, AggregateException will be thrown in case of errors
            // we just take the first one and rethrow it
            catch (AggregateException ex)
            {
                throw ex.InnerExceptions.First();
            }
        }

        protected override string GetNodeDescription()
        {
            return String.Format("FILTER (expression: {0})", this.Expression);
        }

        private static void CheckDataType(DataType dataType)
        {
            if (dataType != DataType.Bool)
                throw new JulaDbException(ErrorCode.TypeError, "The WHERE clause expression should evaluate to a boolean value.");
        }
    }
}
